<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - geometry - spline extrusion</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				font-family: Monospace;
				background-color: #f0f0f0;
				margin: 0px;
				overflow: hidden;
			}
		</style>
	</head>
	<body>

		<script src="../build/three.js"></script>
		<script src="js/controls/OrbitControls.js"></script>

		<!-- where curves formulas are defined -->
		<script src="js/CurveExtras.js"></script>

		<script src="js/libs/stats.min.js"></script>


		<script>
		var container, stats;

		var camera, scene, renderer, splineCamera, cameraHelper, cameraEye;

		var text, plane;

		var targetRotation = 0;
		var targetRotationOnMouseDown = 0;

		var mouseX = 0;
		var mouseXOnMouseDown = 0;

		var windowHalfX = window.innerWidth / 2;
		var windowHalfY = window.innerHeight / 2;

		var binormal = new THREE.Vector3();
		var normal = new THREE.Vector3();


		var pipeSpline = new THREE.CatmullRomCurve3( [
				new THREE.Vector3( 0, 10, -10 ), new THREE.Vector3( 10, 0, -10 ),
				new THREE.Vector3( 20, 0, 0 ), new THREE.Vector3( 30, 0, 10 ),
				new THREE.Vector3( 30, 0, 20 ), new THREE.Vector3( 20, 0, 30 ),
				new THREE.Vector3( 10, 0, 30 ), new THREE.Vector3( 0, 0, 30 ),
				new THREE.Vector3( -10, 10, 30 ), new THREE.Vector3( -10, 20, 30 ),
				new THREE.Vector3( 0, 30, 30 ), new THREE.Vector3( 10, 30, 30 ),
				new THREE.Vector3( 20, 30, 15 ), new THREE.Vector3( 10, 30, 10 ),
				new THREE.Vector3( 0, 30, 10 ), new THREE.Vector3( -10, 20, 10 ),
				new THREE.Vector3( -10, 10, 10 ), new THREE.Vector3( 0, 0, 10 ),
				new THREE.Vector3( 10, -10, 10 ), new THREE.Vector3( 20, -15, 10 ),
				new THREE.Vector3( 30, -15, 10 ), new THREE.Vector3( 40, -15, 10 ),
				new THREE.Vector3( 50, -15, 10 ), new THREE.Vector3( 60, 0, 10 ),
				new THREE.Vector3( 70, 0, 0 ), new THREE.Vector3( 80, 0, 0 ),
				new THREE.Vector3( 90, 0, 0 ), new THREE.Vector3( 100, 0, 0 )
		] );

		var sampleClosedSpline = new THREE.CatmullRomCurve3( [
			new THREE.Vector3( 0, -40, -40 ),
			new THREE.Vector3( 0, 40, -40 ),
			new THREE.Vector3( 0, 140, -40 ),
			new THREE.Vector3( 0, 40, 40 ),
			new THREE.Vector3( 0, -40, 40 )
		] );

		sampleClosedSpline.type = 'catmullrom';
		sampleClosedSpline.closed = true;

		// Keep a dictionary of Curve instances
		var splines = {
			GrannyKnot: new THREE.Curves.GrannyKnot(),
			HeartCurve: new THREE.Curves.HeartCurve( 3.5 ),
			VivianiCurve: new THREE.Curves.VivianiCurve( 70 ),
			KnotCurve: new THREE.Curves.KnotCurve(),
			HelixCurve: new THREE.Curves.HelixCurve(),
			TrefoilKnot: new THREE.Curves.TrefoilKnot(),
			TorusKnot: new THREE.Curves.TorusKnot( 20 ),
			CinquefoilKnot: new THREE.Curves.CinquefoilKnot( 20 ),
			TrefoilPolynomialKnot: new THREE.Curves.TrefoilPolynomialKnot( 14 ),
			FigureEightPolynomialKnot: new THREE.Curves.FigureEightPolynomialKnot(),
			DecoratedTorusKnot4a: new THREE.Curves.DecoratedTorusKnot4a(),
			DecoratedTorusKnot4b: new THREE.Curves.DecoratedTorusKnot4b(),
			DecoratedTorusKnot5a: new THREE.Curves.DecoratedTorusKnot5a(),
			DecoratedTorusKnot5c: new THREE.Curves.DecoratedTorusKnot5c(),
			PipeSpline: pipeSpline,
			SampleClosedSpline: sampleClosedSpline
		};

		extrudePath = new THREE.Curves.TrefoilKnot();

		var dropdown = '<select id="dropdown" onchange="addTube(this.value)">';

		var s;
		for ( s in splines ) {

			dropdown += '<option value="' + s + '"';
			dropdown += '>' + s + '</option>';

		}

		dropdown += '</select>';

		var closed2 = true;
		var parent;
		var tube, tubeMesh;
		var animation = false, lookAhead = false;
		var scale;
		var showCameraHelper = false;

		function addTube() {

			var value = document.getElementById( 'dropdown' ).value;

			var segments = parseInt( document.getElementById( 'segments' ).value );
			closed2 = document.getElementById( 'closed' ).checked;

			var radiusSegments = parseInt( document.getElementById( 'radiusSegments' ).value );

			if ( tubeMesh !== undefined ) parent.remove( tubeMesh );

			extrudePath = splines[ value ];

			tube = new THREE.TubeBufferGeometry( extrudePath, segments, 2, radiusSegments, closed2 );

			addGeometry( tube, 0xff00ff );
			setScale();

		}

		function setScale() {

			scale = parseInt( document.getElementById( 'scale' ).value );
			tubeMesh.scale.set( scale, scale, scale );

		}


		function addGeometry( geometry, color ) {

			// 3d shape

			tubeMesh = THREE.SceneUtils.createMultiMaterialObject( geometry, [
				new THREE.MeshLambertMaterial( {
					color: color
				} ),
				new THREE.MeshBasicMaterial( {
					color: 0x000000,
					opacity: 0.3,
					wireframe: true,
					transparent: true
			} ) ] );

			parent.add( tubeMesh );

		}

		function animateCamera( toggle ) {

			if ( toggle === true ) {

				animation = animation === false;
				document.getElementById( 'animation' ).value = 'Camera Spline Animation View: ' + ( animation ? 'ON' : 'OFF' );

			}

			lookAhead = document.getElementById( 'lookAhead' ).checked;

			showCameraHelper = document.getElementById( 'cameraHelper' ).checked;

			cameraHelper.visible = showCameraHelper;
			cameraEye.visible = showCameraHelper;

		}

		init();
		animate();

		function init() {

			container = document.createElement( 'div' );
			document.body.appendChild( container );

			var info = document.createElement( 'div' );
			info.style.position = 'absolute';
			info.style.top = '10px';
			info.style.width = '100%';
			info.style.textAlign = 'center';
			info.innerHTML = 'Spline Extrusion Examples by <a href="http://www.lab4games.net/zz85/blog">zz85</a><br/>Select spline:';

			info.innerHTML += dropdown;

			info.innerHTML += '<br/>Scale: <select id="scale" onchange="setScale()"><option>1</option><option>2</option><option selected>4</option><option>6</option><option>10</option></select>';
			info.innerHTML += '<br/>Extrusion Segments: <select onchange="addTube()" id="segments"><option>50</option><option selected>100</option><option>200</option><option>400</option></select>';
			info.innerHTML += '<br/>Radius Segments: <select id="radiusSegments" onchange="addTube()"><option>1</option><option>2</option><option selected>3</option><option>4</option><option>5</option><option>6</option><option>8</option><option>12</option></select>';
			info.innerHTML += '<br/>Closed:<input id="closed" onchange="addTube()" type="checkbox" checked />';

			info.innerHTML += '<br/><br/><input id="animation" type="button" onclick="animateCamera(true)" value="Camera Spline Animation View: OFF"/><br/> Look Ahead <input id="lookAhead" type="checkbox" onchange="animateCamera()" /> Camera Helper <input id="cameraHelper" type="checkbox" onchange="animateCamera()" />';

			container.appendChild( info );

			//

			camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 10000 );
			camera.position.set( 0, 50, 500 );

			scene = new THREE.Scene();

			var light = new THREE.DirectionalLight( 0xffffff );
			light.position.set( 0, 0, 1 );
			scene.add( light );

			parent = new THREE.Object3D();
			scene.add( parent );

			splineCamera = new THREE.PerspectiveCamera( 84, window.innerWidth / window.innerHeight, 0.01, 1000 );
			parent.add( splineCamera );

			cameraHelper = new THREE.CameraHelper( splineCamera );
			scene.add( cameraHelper );

			addTube();

			// debug camera

			cameraEye = new THREE.Mesh( new THREE.SphereGeometry( 5 ), new THREE.MeshBasicMaterial( { color: 0xdddddd } ) );
			parent.add( cameraEye );

			cameraHelper.visible = showCameraHelper;
			cameraEye.visible = showCameraHelper;

			//

			renderer = new THREE.WebGLRenderer( { antialias: true } );
			renderer.setClearColor( 0xf0f0f0 );
			renderer.setPixelRatio( window.devicePixelRatio );
			renderer.setSize( window.innerWidth, window.innerHeight );
			container.appendChild( renderer.domElement );

			stats = new Stats();
			container.appendChild( stats.dom );

			//

			controls = new THREE.OrbitControls( camera, renderer.domElement );

			//

			window.addEventListener( 'resize', onWindowResize, false );

		}

		function onWindowResize() {

			windowHalfX = window.innerWidth / 2;
			windowHalfY = window.innerHeight / 2;

			camera.aspect = window.innerWidth / window.innerHeight;
			camera.updateProjectionMatrix();

			renderer.setSize( window.innerWidth, window.innerHeight );

		}

		//

		function animate() {

			requestAnimationFrame( animate );

			render();
			stats.update();

		}

		function render() {

			// Try Animate Camera Along Spline
			var time = Date.now();
			var looptime = 20 * 1000;
			var t = ( time % looptime ) / looptime;

			var pos = tube.parameters.path.getPointAt( t );
			pos.multiplyScalar( scale );

			// interpolation
			var segments = tube.tangents.length;
			var pickt = t * segments;
			var pick = Math.floor( pickt );
			var pickNext = ( pick + 1 ) % segments;

			binormal.subVectors( tube.binormals[ pickNext ], tube.binormals[ pick ] );
			binormal.multiplyScalar( pickt - pick ).add( tube.binormals[ pick ] );


			var dir = tube.parameters.path.getTangentAt( t );

			var offset = 15;

			normal.copy( binormal ).cross( dir );

			// We move on a offset on its binormal
			pos.add( normal.clone().multiplyScalar( offset ) );

			splineCamera.position.copy( pos );
			cameraEye.position.copy( pos );

			// Using arclength for stablization in look ahead.
			var lookAt = tube.parameters.path.getPointAt( ( t + 30 / tube.parameters.path.getLength() ) % 1 ).multiplyScalar( scale );

			// Camera Orientation 2 - up orientation via normal
			if ( !lookAhead ) lookAt.copy( pos ).add( dir );
			splineCamera.matrix.lookAt(splineCamera.position, lookAt, normal);
			splineCamera.rotation.setFromRotationMatrix( splineCamera.matrix, splineCamera.rotation.order );

			cameraHelper.update();

			renderer.render( scene, animation === true ? splineCamera : camera );

		}
	</script>

	</body>
</html>
